local rtk=require('libluartk')
require 'games.heroes3.h3maprw'
local gx=require('libgamex')
local codepages=require('codepages')
table.insert(codepages,'AUTO-DETECT')
local B=require('wxExt.ui_builder')
local DBG=require('wxExt.debug')
require 'translate.trans'
Map=nil
MapOptions=nil
Ret=nil

local config_file=rtk.makePath(rtk.getprop('app.workingDir'),'config/games.ini')
local function getconfig(k)
	return rtk.getini('h3medit',k,config_file)
end
local function setconfig(k,v)
	rtk.setini('h3medit',k,v,config_file)
end

map_encoding=getconfig('encoding') or 'AUTO-DETECT'

local function getstr(pstring)
	return pstring.str
end

function pickFile(parent,prompt,dir,name,filters,flags)
	name=name:gsub('^.*[/\\]','')
	local fileDialog = wx.wxFileDialog(
		parent or wx.NULL,prompt,dir,name,
		filters,
		flags)
	local path
	if fileDialog:ShowModal() == wx.wxID_OK then
		path=fileDialog:GetPath()
	end
	fileDialog:Destroy()
	return path
end

local function eval(obj,filter)
	local code=load('return ' .. filter,'','t',obj)
	return code()
end

function list(filter)
	local count=0
	Ret={}
	rtk.table_ieach(Map.mapObjects,function(k,o)
		if eval(o,filter) then
			table.insert(Ret,o)
			rtk.printf('%d:%s',count,json.encode(o))
			count=count+1
		end
	end)
end

local function exec(line)
	local filter,func,err
	func,err=load(line)
	assert(func,err)
	func()
end

local Actions=B.TActionList{
	{id="IDM_FILE_OPEN",title='Open...\tF3'},
	{id="IDM_FILE_EXIT",title='Exit\tCtrl-Q'},
	{id="IDM_FILE_OPTIONS",title='Options...\tF10'},
	{id="IDM_FILE_EXPORT_STRINGS",title='Export strings...\tCtrl-E'},
	{id="IDM_FILE_TRANSLATE",title='Translate...\tF11'},
	{id="IDM_FILE_COPY_FILENAME",title='Copy file name...\tCtrl-C'},
	{id="IDM_ABOUT",title='About'},
}

local dir='C:\\games\\Heroes3 Fallen Catastrophe HD\\Maps'
Actions.IDM_FILE_OPEN.cmd=function(frame)
	local file=pickFile(nil,'Open .h3m file',dir,'','*.h3m(H3M file)|*.*',wx.wxFD_OPEN+wx.wxFD_FILE_MUST_EXIST)
	if not file then
		return
	end
	dir=wx.wxFileName(file):GetPath()
	frame:doOpenMap(rtk.utf82ansi(file))
end

Actions.IDM_FILE_EXIT.cmd=function(frame)
	frame:Close()
end

Actions.IDM_FILE_OPTIONS.cmd=function(frame)
	local choices={}
	rtk.table_ieach(codepages,function(_,cp)
		table.insert(choices,cp[1])
	end)
	local DlgDef=B.TDialog{
		title='地图设置',
		B.THBox{
			B.TLabel{'字符集'},
			B.TCombo{
				id='encoding',expand=true,proportion=1,
				choices=choices,
				value=map_encoding
			}
		},
		B.THBox{
			B.TButton{id='Ok',wid=wx.wxID_OK},
			B.TButton{id='Cancel',wid=wx.wxID_CANCEL}
		},
	}
	local dlg=DlgDef:create(frame)
	dlg:Fit()
	dlg:SetMinSize(dlg:GetSize())
	if dlg:ShowModal()==wx.wxID_OK then
		local new_encoding=dlg.encoding.Value:upper()
		if new_encoding ~= map_encoding then
			map_encoding=new_encoding
			frame:doOpenMap(frame.FFile)
		end
	end
end

function export_strings(ofile)
	local txt={}
	rtk.table_ieach(Map.strings,function(_,s)
		if s.len>0 then
			local str=string.format('%d,%s',_,json.encode(s.str))
			table.insert(txt,str)
		end
	end)
	txt=table.concat(txt,',\n')
	local fh
	if ofile then
		fh=io.open(ofile,'wt')
		assert(fh,string.format('Cannnot open %s for write.',file))
	else
		fh=io.stdout
	end
	fh:write(txt)
	if ofile then
		fh:close()
	end
end

Actions.IDM_FILE_EXPORT_STRINGS.cmd=function(self)
	local file=pickFile(nil,'Choose output file',dir,'','*.txt(Text file)|*.*',wx.wxFD_SAVE+wx.wxFD_OVERWRITE_PROMPT)
	if not file then
		return
	end
	export_strings(file)
end

Actions.IDM_FILE_COPY_FILENAME.cmd=function(self)
	local clipboard = wx.wxClipboard.Get()
	clipboard:Open()
	local file=rtk.ansi2utf8(self.FFile)
	local textData = wx.wxTextDataObject(file)
	clipboard:SetData(textData)
	clipboard:Close()
	rtk.dbg_print('File name "%s" copied.\n',file)
end

function gzip(data)
	local lz=require('zlib')
	-- remove 789c header & adler checksum tail
	local stream = lz.deflate(6,-15)
	local compressed = stream(data, "finish")
	-- 计算 CRC32 和原始长度
	local crc32 = lz.crc32()(data)
	local isize = #data
	local header = string.char(0x1f,0x8b,8,0,0,0,0,0,0,0xff)
	return table.concat{header,compressed,gx.write_uint32(crc32),gx.write_uint32(isize)}
end

-- 
function load_strings(strlist)
	local patches={}
	rtk.table_ieach(strlist,function(_,p)
		local id=p[1]
		local str=p[2]:gsub('\\(["n])',{
			['\"']='"',
			n='\n'
		})
		rtk.dbg_print('%d=%s\n',id,str)
		local ostr=Map.strings[id]
		ostr.str=str
		table.insert(patches,{pos=ostr.pos,len=ostr.len,str=str})
	end)
	return patches
end

local function transcode_map(o_file,src_encoding,tgt_encoding)
	local map=Map
	local patches={}
	rtk.table_ieach(map.strings,function(_,pstr)
		if pstr.len==0 then
			return
		end
		local nstr=rtk.iconv(src_encoding,tgt_encoding,pstr.str) or '<TRANSLATE ERROR>'
		local nlen=#nstr
		if nstr ~= pstr.str then
			rtk.dbg_print('str %d : offset=0x%x,%d->%d,"%s"\n', _, pstr.pos, pstr.len, nlen, nstr)
			table.insert(patches,{pos=pstr.pos,len=pstr.len,str=nstr})
		end
	end)
	local obuffer=h3MapPatchStrings(Map,patches)
	local ofh=io.open(o_file,'wb')
	assert(ofh, 'Unable to open output file:' .. o_file)
	obuffer=gzip(obuffer)
	ofh:write(obuffer)
	ofh:close()
end

Actions.IDM_FILE_TRANSLATE.cmd=function(frame)
--[[
	local choices={}
	table_ieach(codepages,function(_,cp)
		table.insert(choices,cp[1])
	end)
	local DlgDef=B.TDialog{
		title='翻译设置',
		B.THBox{
			B.TLabel{'目标编码'},
			B.TCombo{
				id='tgt_encoding',expand=true,proportion=1,
				choices=choices,
				value=map_encoding
			}
		},
		B.THBox{
			B.TButton{id='Ok',wid=wx.wxID_OK},
			B.TButton{id='Cancel',wid=wx.wxID_CANCEL}
		},
	}
	local dlg=DlgDef:create(frame)
	dlg:Fit()
	dlg:SetMinSize(dlg:GetSize())
	if dlg:ShowModal()==wx.wxID_OK then
		local new_encoding=dlg.tgt_encoding.Value:upper()
		if new_encoding ~= map_encoding then
			local ifile=frame.FFile
			local ofile=ifile:gsub('.h3m$','-tr.h3m')
			printk(2,'Converting from "%s"\n -> "%s"\n"%s" -> "%s"\n', ifile,ofile,map_encoding, new_encoding)
			transcode_map(ifile,ofile,map_encoding,new_encoding)
		end
	end
]]
	local ifile=frame.FFile
	local ofile=ifile:gsub('.h3m$','-tr.h3m')
	local new_encoding='gb2312'
	rtk.printk(2,'Converting from "%s"\n -> "%s"\n"%s" -> "%s"\n', ifile,ofile,map_encoding,new_encoding)
	transcode_map(ofile,Map.encoding,new_encoding)	
end

Actions.IDM_ABOUT.cmd=function(frame)
	local TAboutGamex=B.TDialog{
		title='About',
		minsize={500,500},
		B.TWebView{
			id='html',
			expand=true,proportion=1,
		},
		B.THBox{
			B.TButton{wid=wx.wxID_OK,title='Ok'},
			B.TButton{wid=wx.wxID_CANCEL,title='Cancel'}
		},
		html_onWebviewLoaded=function(self,evt)
			if not self.FLoaded then
				self.FLoaded=true
				self.html:SetPage([[
				<html>
				<body>
				跨平台的游戏修改工具
				<a href="https://gitee.com/wangdm001/games">https://gitee.com/wangdm001/games</a>
				</body>
				</html>
				]],'')
			end
		end,
	}
	local dlg=TAboutGamex:create(frame)
	dlg:ShowModal()
end

local TMapDialog=B.TFrame{
	title='Heroes3 Map editor',
	minsize={800,600},
	--sizeable=true,
	menu=B.TMenuBar{
		actions=Actions,
		B.TPopupMenu{
			title="文件(&F)",
			"IDM_FILE_OPEN",
			"IDM_FILE_OPTIONS",
			"IDM_FILE_EXPORT_STRINGS",
			"IDM_FILE_TRANSLATE",
			"IDM_FILE_COPY_FILENAME",
			"IDM_FILE_EXIT",
		},
		B.TPopupMenu{
			title="帮助(&H)",
			"IDM_ABOUT"
		},
	},
	B.TStatusBar{cols=3,id='statusbar'},
	B.TNotebook{
		expand=true,proportion=1,
		B.TPanel{
			title='所有物体',
			B.TTreeList{
				id='listAll',
				expand=true,proportion=1,
				cols={'No.','ObjIdx','Pos','Json'},
			},
		},
		B.TPanel{
			title='先知',
			B.TGrid{
				id='listSeersHuts',
				expand=true,proportion=1,
				cols={'Pos','QType','Content','RType','Reward'},
			}
		},
		B.TPanel{
			title='守卫',
			B.TGrid{
				id='listQuestGuards',
				expand=true,proportion=1,
				cols={'Pos','QType','Content'},
			}
		},
		B.TPanel{
			title='Events',
			B.TGrid{
				id='listEvents',
				expand=true,proportion=1,
				cols={'Pos','消息','资源','经验','生物','宝物','技能','四围','魔法','魔法点','其它'},
			}
		},
		B.TPanel{
			title='Strings',
			B.THBox{
				expand=true,
				B.TLabel{'Language'},
				B.TCombo{id='cmbLanguage',title='zh',proportion=1},
				B.TLabel{'Encoding'},
				B.TCombo{id='cmbEncoding',title='gb2312',proportion=1},
				B.TButton{id='btnTranslate',title='翻译'},
				B.TCombo{id='cmbTranslate',title='zh',proportion=1},
				B.TButton{id='btnTranscode',title='转码并保存'},
				B.TCombo{id='cmbTranscode',title='gb2312',proportion=1},
			},
			B.TGrid{
				id='listStrings',
				expand=true,proportion=1,
				cols={'ObjIdx','Pos','Len','String'}
			}
		},
	},
	DBG.ID_DEBUG_BAR{id='dbar'},
	clearList=function(list,objs)
		list:Freeze()
		if list:GetNumberRows()>0 then
			list:DeleteRows(0,list:GetNumberRows())
		end
		list:AppendRows(#objs)	
	end,
	listAll_onDataviewSelectionChanged=function(self,evt)
		local list=self.listAll
		local item=list:GetSelection()
		if item then
			local o=self.FObjectIndex[item:GetValue()]
			if o then
				list:SetToolTip(rtk.tableToLuaCode(o))
			else
				list:UnsetToolTip()
			end
		end
		evt:Skip()
	end,
	doUpdateEvents=function(self)
		local list=self.listEvents
		local objs={}
		rtk.table_ieach(Map.mapObjects,function(_,o)
			if o.obj.typeName=='Event' then
				table.insert(objs,o)
			end
		end)
		self.F_Env.events=objs
		self.clearList(list,objs)
		local row,o
		local function setcol(col,content)
			list:SetCellValue(row,col,content)
		end
		for _,o in ipairs(objs) do
			row=_-1
			list:SetRowLabelValue(_-1,tostring(_))
			setcol(0,string.format('(%d,%d,%d)',o.pos.x,o.pos.y,o.pos.z))
			local oo=o.obj
			if oo.msg then
				setcol(1,getstr(oo.msg))
			end
			local i
			for i=1,7 do
				if oo.resources[i]>0 then
					setcol(2,json.encode(oo.resources))
					break
				end
			end
			if oo.exp>0 then
				setcol(3,tostring(oo.exp))
			end
			if #oo.monsters>0 then
				setcol(4,json.encode(oo.monsters))
			end
			if #oo.artifacts>0 then
				setcol(5,json.encode(oo.artifacts))
			end
			if #oo.secondarySkills>0 then
				setcol(6,json.encode(oo.secondarySkills))
			end
			for i=1,4 do
				if oo.primarySkills[i]~=0 then
					setcol(7,json.encode(oo.primarySkills))
					break
				end
			end
			if #oo.spells>0 then
				setcol(8,json.encode(oo.spells))
			end
			if oo.mana~=0 then
				setcol(9,tostring(oo.mana))
			end
			if oo.morale~=0 or oo.luck ~=0 then
				setcol(10,json.encode{士气=oo.morale,幸运=oo.luck})
			end
		end
		list:AutoSizeColumns()
		list:Thaw()
	end,
	doUpdateGuards=function(self)
		local list=self.listQuestGuards
		local objs={}
		rtk.table_ieach(Map.mapObjects,function(_,o)
			if o.obj.typeName=='QuestGuard' then
				table.insert(objs,o)
			end
		end)
		self.clearList(list,objs)
		self.F_Env.questGuards=objs
		local _,o
		for _,o in ipairs(objs) do
			list:SetRowLabelValue(_-1,tostring(_))
			list:SetCellValue(_-1,0,string.format('(%d,%d,%d)',o.pos.x,o.pos.y,o.pos.z))
			local q=o.obj
			local content=''
			list:SetCellValue(_-1,1,q.qname)
			if q.qname=='ReturnWithArtifacts' then
				content=json.encode(q.artifacts)
			elseif q.qname=='ReturnWithCreatures' then
				content=json.encode(q.creatures)
			elseif q.qname=='BeASpecificHero' then
				content=q.heroId
			elseif q.qname=='DefeatASpecificHero' then
				local h=Map.mapObjects[q.refId]
				content=string.format('%s at (%d,%d,%d)',h.obj.id.name,h.pos.x,h.pos.y,h.pos.z)
			elseif q.qname=='DefeatASpecificMonster' then
				local h=Map.mapObjects[q.refId]
				local name=H3Creatures[h.obj.sid]
				content=string.format('%s at (%d,%d,%d)',name,h.pos.x,h.pos.y,h.pos.z)			
			end
			list:SetCellValue(_-1,2,content)
		end
		list:AutoSizeColumns()
		list:Thaw()
	end,
	doUpdateHuts=function(self)
		local list=self.listSeersHuts
		local huts={}
		rtk.table_ieach(Map.mapObjects,function(_,o)
			if o.obj.typeName=='SeersHut' then
				table.insert(huts,o)
			end
		end)
		self.F_Env.huts=huts
		list:Freeze()
		if list:GetNumberRows()>0 then
			list:DeleteRows(0,list:GetNumberRows())
		end
		list:AppendRows(#huts)
		rtk.table_ieach(huts,function(_,h)
			list:SetRowLabelValue(_-1,tostring(_))
			list:SetCellValue(_-1,0,string.format('(%d,%d,%d)',h.pos.x,h.pos.y,h.pos.z))
			local q=h.obj.quest
			list:SetCellValue(_-1,1,q.qname)
			local content=''
			if q.qname=='ReturnWithArtifacts' then
				content=json.encode(q.artifacts)
			elseif q.qname=='DefeatASpecificHero' then
				local h=Map.mapObjects[q.refId]
				content=string.format('%s at (%d,%d,%d)',h.obj.id.name,h.pos.x,h.pos.y,h.pos.z)
			elseif q.qname=='BeASpecificHero' then
				content=string.format('%s',q.heroId)
			end
			list:SetCellValue(_-1,2,content)
			local r=rtk.table_clone(h.obj.reward)
			list:SetCellValue(_-1,3,r.type)
			r.type=nil
			list:SetCellValue(_-1,4,json.encode(r))
		end)
		list:AutoSizeColumns()
		list:Thaw()
	end,
	doUpdateAllObjs=function(self)
		local list=self.listAll
		local objs={}
		local indices={}
		self.FObjectIndex=indices
		rtk.table_ieach(Map.mapObjects,function(_,o)
			local tp=o.obj.typeName
			local tbl=objs[tp]
			if not tbl then
				tbl={type=tp}
				objs[tp]=tbl
				table.insert(objs,tbl)
			end
			o.originalIdx=_
			table.insert(tbl,o)
		end)
		table.sort(objs,function(a,b) return a.type<b.type end)
		self.FAllObjects=objs
		list:Freeze()
		list:DeleteAllItems()
		local ritem=list:GetRootItem()
		rtk.table_ieach(objs,function(_,tbl)
			local titem=list:AppendItem(ritem,tbl.type)
			rtk.table_ieach(tbl,function(_,o)
				local item=list:AppendItem(titem,tostring(_))
				indices[item:GetValue()]=o
				list:SetItemText(item,1,tostring(o.originalIdx))
				list:SetItemText(item,2,string.format('(%d,%d,%d)',o.pos.x,o.pos.y,o.pos.z))
				list:SetItemText(item,3,json.encode(o))
			end)
		end)
		list:Thaw()
	end,
	doUpdateStrings=function(self,no_transcode)
		Map.encoding=map_encoding
		Map.lang=''
		if not no_transcode then
			if map_encoding:upper() == 'AUTO-DETECT' then
				local encoding,lang=h3MapDetectEncoding(Map)
				if encoding=='ASCII' then encoding='UTF-8' end
				Map.encoding=encoding
				Map.lang=lang
				if encoding and encoding ~= 'UTF-8' then
					rtk.table_ieach(Map.strings,function(_,ps)
						if ps.len>0 then
							ps.str=rtk.iconv(encoding,'UTF-8',ps.str) or '<encoding error>'
						end
					end)
				end
			elseif map_encoding ~= 'UTF-8' then
				rtk.table_ieach(Map.strings,function(_,ps)
					if ps.len>0 then
						ps.str=rtk.iconv(map_encoding,'UTF-8',ps.str) or '<encoding error>'
					end
				end)
			end
		end
		local list=self.listStrings
		list:Freeze()
		if list:GetNumberRows()>0 then
			list:DeleteRows(0,list:GetNumberRows())
		end
		local row=0
		rtk.table_ieach(Map.strings,function(_,str)
			if str.len>0 then
				list:AppendRows(1)
				list:SetRowLabelValue(row,tostring(row))
				list:SetCellValue(row,0,tostring(_))
				list:SetCellValue(row,1,string.format('0x%x',str.pos))
				list:SetCellValue(row,2,tostring(str.len))
				list:SetCellValue(row,3,json.encode(str.str))
				row=row+1
			end
		end)
		list:AutoSizeColumns()
		list:Thaw()
		self:SetStatusText(rtk.ansi2utf8(self.FFile),0)
		self:SetStatusText(Map.encoding,1)
		self:SetStatusText(Map.lang or '',2)
	end,
	onSize=function(self,evt)
		local sz=self:GetClientSize()
		self:GetStatusBar():SetStatusWidths{sz.Width*0.8,-1,-1}
		evt:Skip()
	end,
	doOpenMap=function(self,file)
		local ok,err
		ok,err=h3MapReadFile(file,MapOptions)
		assert(ok,string.format('Cannot open "%s"\n%s',arg[1],err))
		Map=ok
		rtk.table_ieach(Map.mapObjects,function(_,o)
			if o.obj.identifier then
				Map.mapObjects[o.obj.identifier]=o
			end
		end)
		self.FFile=file
		self.F_Env.Map=Map
		self:doUpdateHuts()
		self:doUpdateGuards()
		self:doUpdateEvents()
		self:doUpdateAllObjs()
		self:doUpdateStrings()
	end,
	doStatusClick=function(self,i)
		rtk.dbg_print('Status click on %d\n', i)
		if i==1 then
			-- popup encoding select menu
		end
	end,
	statusbar_onLeftDown=function(self,evt)
		local pos=evt:GetPosition()
		evt:Skip()
		local i,N
		local status=self.statusbar
		N=status:GetFieldsCount()
		local x=pos.X
		local rect=wx.wxRect()
		for i=0,N-1 do
			if status:GetFieldRect(i,rect) and rect.Left<x and rect.Left+rect.Width>x then
				self:doStatusClick(i)
				break
			end
		end
	end,
	btnTranscode_onClick=function(self)
		local tgt=self.cmbTranscode.Value
		local file=rtk.ansi2utf8(self.FFile):gsub('%.h3m','-' .. tgt .. '.h3m')
		pickFile(nil,'Choose output file',dir,file,'*.h3m(Heroes3 map file)|*.*',wx.wxFD_SAVE+wx.wxFD_OVERWRITE_PROMPT)
		if not file then
			return
		end
		local _,s
		local strings=Map.strings
		local cache={}
		local patches={}
		for _,s in ipairs(strings) do
			if s.len>0 then
				local str=cache[s.str]
				if not str then
					str=rtk.iconv('utf-8',tgt,s.str)
					cache[s.str]=str
					rtk.dbg_print('%d,%s\n',_,str)
				else
					rtk.dbg_print('%d,cached=%s\n',_,str)
				end
				s.str=str
				table.insert(patches,{pos=s.pos,len=s.len,str=str})
			end
		end
		local obuffer=h3MapPatchStrings(Map,patches)
		local ofh=io.open(rtk.utf82ansi(file),'wb')
		assert(ofh, 'Unable to open output file:' .. file)
		obuffer=gzip(obuffer)
		ofh:write(obuffer)
		ofh:close()
	end,
	btnTranslate_onClick=function(self)
		local _,s
		local tgt=self.cmbTranslate.Value
		local strings=Map.strings
		local cache={}
		for _,s in ipairs(strings) do
			if s.len>0 then
				local str=cache[s.str]
				if not str then
					str=xiaoniu_translate_text(s.str,'auto',tgt)
					cache[s.str]=str
					rtk.dbg_print('%d,%s\n',_,str)
					os.msleep(100)
				else
					rtk.dbg_print('%d,cached=%s\n',_,str)
				end
				s.str=str
			end
		end
		self:doUpdateStrings(true)
	end,
	onCreate=function(self)
	end
}

function main(exe,file)
	if rtk.getpropFlag('export-strings') then
		local ok,err
		ok,err=h3MapReadFile(file,MapOptions)
		assert(ok,string.format('Cannot open "%s"\n%s',arg[1],err))
		Map=ok
		export_strings()
		return
	end
	local dlg=TMapDialog:create()
	map_encoding=map_encoding:upper()
	if file then
		dlg:doOpenMap(file)
	end
	dlg:Show()
	wx.wxGetApp():MainLoop()
end

if not rtk.getpropFlag('#.startAsExe') then
	main('wmclua',...)
end
